﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using SmartStringResources.CodeDom;

namespace SmartStringResources
{
    internal class SmartResourcesBuilder
    {
        private readonly ResourceArea _area;
        private readonly string _resourceClassFullName;

        public SmartResourcesBuilder(ResourceArea area, string resourceClassFullName)
        {
            _area = area;
            _resourceClassFullName = resourceClassFullName;
        }

        public CodeTypeDeclaration Build(AccessibilityLevel accessibilityLevel)
        {
            var codeClass = new CodeTypeDeclaration(_area.ValidIdentifierName)
            {
                IsClass = true,
                TypeAttributes = accessibilityLevel.GetTypeAttribute() | TypeAttributes.Class,
            };
            codeClass.Comments.AddSummaryComments(Strings.GeneratorResources.ResourcesClassComments());
            codeClass.AddDefaultAttributes();
            var resourceClass = new CodeTypeReferenceExpression(_resourceClassFullName) { Type = { Options = CodeTypeReferenceOptions.GlobalReference } };

            foreach (var resourceValue in _area.Values)
                codeClass.Members.Add(GetResourceMethod(resourceValue, resourceClass));

            foreach (var nestedArea in _area.NestedAreas)
            {
                var smartResourceBuilder = new SmartResourcesBuilder(nestedArea, _resourceClassFullName);
                codeClass.Members.Add(smartResourceBuilder.Build(accessibilityLevel));
            }

            return codeClass;
        }
        
        private CodeMemberMethod GetResourceMethod(ResourceValue resourceValue, CodeTypeReferenceExpression resourceClass)
        {
            var method = CreateMethod(resourceValue);

            method.Statements.Add(GetCultureInfoCheckBlock(GetNoCultureBlock(resourceClass, resourceValue)));
            method.Statements.Add(GetCultureInfoBlock(resourceClass, resourceValue));

            return method;
        }

        private CodeMemberMethod CreateMethod(ResourceValue resourceValue)
        {
            var method = new CodeMemberMethod { Name = resourceValue.ValidIdentifierName, Attributes = MemberAttributes.Public | MemberAttributes.Static, ReturnType = new CodeTypeReference(typeof(string)) };
            method.Comments.AddSummaryComments(Strings.GeneratorResources.ResourcesMemberComments(resourceValue: resourceValue.Value));

            method.Parameters.AddRange(GetMethodParameters(resourceValue).ToArray());
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(CultureInfo), Constants.CultureInfoParameterName) { CustomAttributes = new CodeAttributeDeclarationCollection(new[] { new CodeAttributeDeclaration(new CodeTypeReference(typeof(OptionalAttribute))) }) });

            return method;
        }

        private IEnumerable<CodeParameterDeclarationExpression> GetMethodParameters(ResourceValue resourceValue)
        {
            return resourceValue.FormatNames.Select(parameter => new CodeParameterDeclarationExpression(typeof(object), parameter.ValidIdentifierName));
        }

        private CodeStatement GetCultureInfoBlock(CodeTypeReferenceExpression resourceClass, ResourceValue resourceValue)
        {
            var resourceManager = new CodePropertyReferenceExpression(resourceClass, Constants.ResourceManagerProperty);
            var cultureInfoParameterReference = new CodeVariableReferenceExpression(Constants.CultureInfoParameterName);
            var invokeGetStringExpression = new CodeMethodInvokeExpression(resourceManager, Constants.GetStringMethod, new CodePrimitiveExpression(resourceValue.ResourceKey), cultureInfoParameterReference);

            return new CodeMethodReturnStatement(resourceValue.HasFormat ? GetFormatWithInvokeExpression(resourceValue, invokeGetStringExpression) : invokeGetStringExpression);
        }

        private CodeStatement GetCultureInfoCheckBlock(CodeExpression noCultureBlock)
        {
            return new CodeConditionStatement(
                new CodeMethodInvokeExpression(
                    new CodeTypeReferenceExpression(typeof(object)), "ReferenceEquals", new CodeVariableReferenceExpression(Constants.CultureInfoParameterName), new CodePrimitiveExpression(null)),
                new CodeMethodReturnStatement(noCultureBlock));
        }

        private CodeExpression GetNoCultureBlock(CodeTypeReferenceExpression resourceClass, ResourceValue resourceValue)
        {
            var resourceProperty = new CodePropertyReferenceExpression(resourceClass, resourceValue.ValidIdentifierResourceKey);

            if (resourceValue.HasFormat)
                return GetFormatWithInvokeExpression(resourceValue, resourceProperty);

            return resourceProperty;
        }

        private CodeExpression GetFormatWithInvokeExpression(ResourceValue resourceValue, CodeExpression resourceAccessExpression)
        {
            // Tuple.Create("parameter", parameter);
            var parameters = resourceValue.FormatNames.Select(parameter => new CodeMethodInvokeExpression(
                new CodeTypeReferenceExpression(typeof(Tuple)),
                "Create",
                new CodePrimitiveExpression(parameter.Name),
                new CodeVariableReferenceExpression(parameter.ValidIdentifierName)
                ));

            var invokeParameters = new[] { resourceAccessExpression }.Concat(parameters);

            // StringFormatWithExtension.FormatWith(Resources.Resource, parameters);
            return new CodeMethodInvokeExpression(
                new CodeTypeReferenceExpression(typeof(StringFormatWithExtension)),
                Reflection.GetMemberName(() => StringFormatWithExtension.FormatWith(null, null)),
                invokeParameters.ToArray()
                );
        }
    }
}